package team.scau.laboratory_repair.service;

import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.*;
import org.activiti.engine.history.*;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import team.scau.laboratory_repair.common.activiti.dto.ProcDefDto;
import team.scau.laboratory_repair.common.utils.DateTool;
import team.scau.laboratory_repair.config.WebConf;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * @author 30254
 * creadtedate:2018/10/13
 */
@Service
public class ActivitiService {

    @Autowired
    private TaskService taskService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private ProcessEngineConfiguration processEngineConfiguration;

    @Autowired
    private UserService userService;

    private static final String PROC_LAB_REPAIR = "labRepair";

    private static final String PROC_LAB_REPAIR_RE = "labRepairRe";

    private ProcessDiagramGenerator pdg = new DefaultProcessDiagramGenerator();

    public List<ProcDefDto> listProcDef(String name) {
        List<ProcessDefinition> list;
        if (name != null && !name.isEmpty()) {
            list = repositoryService.createProcessDefinitionQuery().processDefinitionName(name).list();
        } else {
            list = repositoryService.createProcessDefinitionQuery().list();
        }
        List<ProcDefDto> dtoList = new ArrayList<>();
        for (ProcessDefinition p : list) {
            dtoList.add(new ProcDefDto(p));
        }
        return dtoList;
    }

    private String getDeployIdByProcDefId(String id) {
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).list();
        return list.get(0).getDeploymentId();
    }

    public InputStream viewProcessDefPng(String id) {
        String deployId = getDeployIdByProcDefId(id);
        String filename = id.substring(0, id.indexOf(':')) + ".png";
        return repositoryService.getResourceAsStream(deployId, filename);
    }

    public void deployProcDef(String name, String key, String zip) throws FileNotFoundException {
        String pre = WebConf.UPLOAD_PATH;
        pre = pre.substring(0, pre.lastIndexOf(File.separatorChar));
        zip = pre + zip;
        repositoryService.createDeployment().name(name)
                .key(key)
                .addZipInputStream(new ZipInputStream(new FileInputStream(zip)))
                .deploy();
    }

    public void deleteProcDef(String id) {
        String deployId = getDeployIdByProcDefId(id);
        repositoryService.deleteDeployment(deployId, true);
    }

    public Task getUserTaskByProcId(String procId) {
        return taskService.createTaskQuery().processInstanceId(procId).singleResult();
    }

    public List<ProcessInstance> getUserProcByAssignee(String assignee) {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(assignee).list();
        Set<String> ids = new HashSet<>();
        for (Task t : tasks) {
            ids.add(t.getProcessInstanceId());
        }
        if(ids.size() == 0){
            return new ArrayList<>();
        }
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).includeProcessVariables().list();
    }

    public List<Task> getUserTaskByCandidate(String assignee) {
        return taskService.createTaskQuery().taskInvolvedUser(assignee).taskUnassigned().list();
    }

    public List<ProcessInstance> getProcByTasks(List<Task> tasks) {
        Set<String> ids = new HashSet<>();
        for (Task t : tasks) {
            ids.add(t.getProcessInstanceId());
        }
        if (ids.size()==0){
            return new ArrayList<>();
        }
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).includeProcessVariables().list();
    }

    public List<Task> getTasksByProc(String procId) {
        return taskService.createTaskQuery().processInstanceId(procId).list();
    }

    public Task getTaskById(String id) {
        return taskService.createTaskQuery().taskId(id).singleResult();
    }

    public void claim(String id, String assigneeId) throws Exception {
        String assignee = taskService.createTaskQuery().taskId(id).singleResult().getAssignee();
        if(StringUtils.isNotBlank(assignee)){
            throw new Exception("task cannot be claimed twice");
        }
        taskService.claim(id, assigneeId);
    }

    public void unclaim(String id) throws Exception {
        String assignee = taskService.createTaskQuery().taskId(id).singleResult().getAssignee();
        if(StringUtils.isBlank(assignee)){
            throw new Exception("task has been no assignee");
        }
        taskService.unclaim(id);
    }

    public ProcessInstance startProc(String key) {
        return startProcWithData(key, new HashMap<>());
    }

    public ProcessInstance startProcWithData(String key, Map<String, Object> data) {
        return runtimeService.startProcessInstanceByKey(key, data);
    }

    public InputStream traceProcessImage(String processInstanceId) {
        // 经过的节点
        HistoricProcessInstance processInstance =
                historyService.createHistoricProcessInstanceQuery().includeProcessVariables().processInstanceId(processInstanceId).singleResult();
        //获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        List<HistoricActivityInstance> highLightedActivityList =
                historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
        //高亮环节id集合
        List<String> highLightedActivities = new ArrayList<>();
        for (HistoricActivityInstance tempActivity : highLightedActivityList) {
            String activityId = tempActivity.getActivityId();
            highLightedActivities.add(activityId);
        }
        //高亮线路id集合
        List<String> highLightedFlows = getHighLightedFlows(bpmnModel, highLightedActivities);


        return pdg.generateDiagram(bpmnModel, highLightedActivities, highLightedFlows);

    }

    private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<String> highLightedActivities) {
        // 用以保存高亮的线flowId
        List<String> highFlows = new ArrayList<>();
        for (Process process : bpmnModel.getProcesses()) {
            Collection<FlowElement> flowElements = process.getFlowElements();
            for (FlowElement flowElement : flowElements) {
                if (flowElement instanceof SequenceFlow) {
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                    if (highLightedActivities.contains(sequenceFlow.getSourceRef()) &&
                            highLightedActivities.contains(sequenceFlow.getTargetRef())) {
                        highFlows.add(sequenceFlow.getId());
                    }
                }
            }
        }
        return highFlows;
    }

    public void setTaskVar(String id, String key, Object value) {
        taskService.setVariable(id, key, value);
    }

    public void setTaskVars(String id, Map<String, Object> data) {
        taskService.setVariables(id, data);
    }

    public <T> T getTaskVar(String id, String name, Class<T> type) {
        return type.cast(historyService.createHistoricVariableInstanceQuery()
                .taskId(id).variableName(name).singleResult());
    }

    public <T> T getProcVar(String id, String name, Class<T> type) {
        return type.cast(historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(id).variableName(name).singleResult());
    }

    public Map<String, Object> getProcVars(String id) {
        List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(id).list();
        Map<String, Object> map = new HashMap<>();
        for (HistoricVariableInstance v : list){
            map.put(v.getVariableName(), v.getValue());
        }
        return map;
    }

    public void complete(String id) {
        completeWithData(id, new HashMap<>());
    }

    public void completeWithData(String id, Map<String, Object> data) {
        taskService.complete(id, data);
    }

    public HistoricProcessInstance getProcHisById(String id){
        return historyService.createHistoricProcessInstanceQuery().includeProcessVariables().processInstanceId(id).singleResult();
    }

    public HistoricVariableInstance getVarByNameAndValue(String name, Object value){
        return historyService.createHistoricVariableInstanceQuery().variableValueEquals(name, value).singleResult();
    }

    public List<HistoricVariableInstance> getVarByName(String name){
        return historyService.createHistoricVariableInstanceQuery().variableName(name).list();
    }

    public List<HistoricTaskInstance> listHistoryTaskByProcId(String procId){
        return historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(procId).orderByHistoricTaskInstanceStartTime().desc().list();
    }

    public List<HistoricProcessInstance> listHistoryProcByIdSetAndDateRangeAndFinished(Set<String> procIds, Date after, Date before, Boolean finished){
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().includeProcessVariables();
        if (after != null) {
            query = query.startedAfter(after);
        } else {
            query = query.startedAfter(DateTool.getLastMonthDate());
        }
        if (before != null) {
            query = query.startedBefore(before);
        }
        if (Boolean.TRUE.equals(finished)) {
            query = query.finished();
        }else if(Boolean.FALSE.equals(finished)) {
            query = query.unfinished();
        }
        if (procIds != null) {
            if (procIds.size() != 0){
                query = query.processInstanceIds(procIds);
            }else {
                return new ArrayList<>();
            }
        }
        return query.orderByProcessInstanceStartTime().desc().list();
    }

    public Set<String> listHistoryProcIdByAssignee(String assignee) {
        List<HistoricTaskInstance> pTaskIds = historyService.createHistoricTaskInstanceQuery().taskAssignee(assignee)
                .orderByHistoricTaskInstanceStartTime().desc().list();
        Set<String> procIds = new LinkedHashSet<>();
        for (HistoricTaskInstance h : pTaskIds) {
            procIds.add(h.getProcessInstanceId());
        }
        return procIds;
    }

    public Set<String> listHistoryProcIdByVarNameAndValueList(String name, List<?> valueList) {
        List<HistoricVariableInstance> vars = getVarByName(name);
        Set<String> procIds = new LinkedHashSet<>();
        for (HistoricVariableInstance h : vars) {
            if(valueList.contains(h.getValue())) {
                procIds.add(h.getProcessInstanceId());
            }
        }
        return procIds;
    }

    public HistoricProcessInstance selectHistoryProcById(String id) {
        return historyService.createHistoricProcessInstanceQuery().includeProcessVariables().processInstanceId(id).singleResult();
    }
}
